import {AjaxParam,RestrictParam,ResultEntity,RequestSuccess,load, save} from "./index";


const globelBaseURL = "/fwApi"

// 用于防抖
const set = new Set<string>();

// 用于节流
const restrictMap = new Map<string, any>();


/**
 * 发起异步请求
 * @param param 请求参数
 * @param T 响应正文数据（IResultEntity）中，data属性的泛型类型
 * @param D 请求表单的泛型类型
 * @returns 
 */
export const request = <T = any,D = any>({
	url,
	retry = 0,
	useCache = true, 
	method = "POST",
	data,
	header,
	baseURL = globelBaseURL}: AjaxParam<D>): Promise<RequestSuccess<ResultEntity<T>>> => {

    // 从vuex中读取token，放入头信息的Authorization字段中
    const token = uni.getStorageSync("token");
    // console.log("token = ",token);
	
    if (token) {
		if (!header) {
			header = {};
		}
        header.Authorization = token;
    }

    // 将请求参数json序列化（我的系统没有考虑get的url参数，自己写的接口偷懒全用的post）
    const jsonStr = data ? JSON.stringify(data) : "";

    // 将url、请求参数、请求方法拼接在一起形成key
    const key = url + jsonStr + method;

    /**
     * 发起异步请求
     * @returns 
     */
    const doAjax = (): Promise<any> => {
        // 如果使用缓存
        if (useCache) {
            // 加载缓存数据，如果有缓存数据直接返回不进行网络请求
            const obj = load(key);
            if (null != obj) {
                set.delete(key);
                // 返回自定义的状态码，表示请求在本地进行了解决：308
                return Promise.resolve({ data: obj, statusCode: 308, errMsg: "缓存命中" });
            }
        }

        // key也用于防抖判断，如果上次同样的请求还没有结束，不允许重复提交
        if (set.has(key)) {
			console.log("重复请求的key = ",key);
            return Promise.reject({ data: { code: -1, msg: "请勿重复提交" }, statusCode: 308, errMsg: "请勿重复提交" });
        }
        // 当前key添入到set中，以便进行防抖判断
        set.add(key);


        // 这里做个简单判断，如果url包括/insert，/update，/delete那么来个遮罩
        // if (url?.indexOf("/insert") != -1
        //     || url?.indexOf("/update") != -1
        //     || url?.indexOf("/delete") != -1
        // ) {
        //     loadingInstance = ElLoading.service();
        // }

        // console.log("baseURL = ", baseURL);
		const promise = new Promise<RequestSuccess<ResultEntity<T>>>((resolve,reject) => {
			
			uni.request({
				url: baseURL + url,
				data,
				header,
				method,
				success(res) {
					const statusCode = res.statusCode;
					if (statusCode == 200) {
						const resData = res.data as ResultEntity;
						if (resData.code == -41
						|| resData.code == -42
						|| resData.code == -43) {
							uni.showToast({
								icon:"error",
								title: "登录失效，请重新登录",
							});
						// 需要提示消息的回执
						} else if (resData.code == 3) {
							uni.showToast({
								icon:"success",
								title: resData.msg,
							});
						}
						resolve(res as RequestSuccess<ResultEntity<T>>);
					} else {
						reject({data: {code: -1,msg: res.errMsg},statusCode,errMsg: "请求发生错误"})
					}
				},
				fail(error) {
					console.log("uni.request发生异常。")
					reject(error);
				}
			})
		});

        return promise;
    }

    /**
     * 重试（内部递归调用），调用doAjax进行异步请求，当异步请求发生错误的时候进行重试
     * @param timeout 重试等待时间，下次重试等待时间为上次的1.5倍
     * @param retryCount 当前重试次数 
     * @returns 
     */
    const doRetry = async (timeout = 3000, retryCount = 1): Promise<RequestSuccess<ResultEntity<T>>> => {
        try {
            return await doAjax();
        } catch (e) {
            if (retryCount <= retry) {
                return doRetry(timeout * 1.5, retryCount + 1);
            } else {
                throw e;
            }
        }
    }

    // 调用异步请求，或者进行重试调用
    const promise: Promise<RequestSuccess<T>> = retry <= 0 ? doAjax() : doRetry(3000, 1);

    return new Promise<RequestSuccess<ResultEntity<T>>>((resolve) => {
        promise
            .then(res => {
				// 登录失效
				if (res.data.code == -42) {
					uni.showToast({
						icon: "error",
						title: "登录信息已失效，请重新登录",
						
						success() {
							uni.navigateTo({
								url: "/pages/login"
							});
						}
					})
					
                // 服务器逻辑处理出错
				} else if (res.data.code < 0) {
                    uni.showToast({ icon: "error", title: res.data.msg });
                    // 服务器成功处理逻辑：缓存数据
                } else {
                    // 非从本地缓存加载数据的情况下，对数据进行缓存
                    if (res.statusCode != 308 && useCache) {
                        save(url,
                            key,
                            res.data);
                    }

                }
                resolve(res as RequestSuccess<ResultEntity<T>>);
            })
            // 通过HTTP状态码判断的错误处理
            .catch(error => {
                if (error.statusCode == 308) {
                    console.log("请勿重复提交：", key);
                    // uni.showToast({ icon: "error", title: "请勿重复提交" });
                } else {
                    console.log("响应失败：", error);
                    uni.showToast({ icon: "error", title: "网络错误" });
					resolve({statusCode: 500,header: null,cookies: [],data: {code: -31,msg: "网络错误"}});
                }
            })
            .finally(() => {
                // 如果生成了key表示使用了缓存
                set.delete(key);
            })
    })
}

/**
 * 根据key或url进行节流，如果传入key值，那么使用key值进行节流，否则根据url进行节流
 * @param param 
 * @param timeout 节流时长 
 */
const restrict = <T = any>(param: RestrictParam):Promise<RequestSuccess<ResultEntity<T>>> => {
    const key = param.key || param.url || "";
    const timeoutId = restrictMap.get(key);
    if (null != timeoutId) {
        clearTimeout(timeoutId);
    }

    const restrictTimeout = param.restrictTimeout || 3000;

    return new Promise((resolve, reject) => {
        const id = setTimeout(() => {
            request(param).then(res => {
                resolve(res)
            }, error => {
                reject(error)
            }).finally(() => {
                restrictMap.delete(key);
            });
        }, restrictTimeout);
        restrictMap.set(key, id);
    })
}

export {request as default,restrict};
